﻿/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * ZIP Entry Class
 * 		Represents and manages an entry in a PK-ZIP file.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.IO.Compression;
using Adaptive.Foundation.IO;
using Adaptive.Win32;

namespace Adaptive.Foundation.IO.Zip
{
    /// <summary>
    /// Represents and manages an entry in a PK-ZIP file.
    /// </summary>
    /// <remarks>
    /// This class provides the mechanism for compression and decompression of the data.
    /// </remarks>
    public sealed class ZipEntry : AiObjectBase, IStorable
    {
        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Local file header record.
        /// </summary>
        private ZipLocalFileHeader _localHeader;
        /// <summary>
        /// Compressed data content.
        /// </summary>
        private byte[] _compressedData;
        /// <summary>
        /// Data descriptor instance.
        /// </summary>
        private ZipDataDescriptor _dataDescriptor;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the <see cref="ZipEntry" /> class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public ZipEntry()
            : base()
        {
            _localHeader = new ZipLocalFileHeader();
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><b>true</b> to release both managed and unmanaged resources;
        /// <b>false</b> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                UnloadData();
                if (_localHeader != null)
                    _localHeader.Dispose();

                if (_dataDescriptor != null)
                    _dataDescriptor.Dispose();
            }

            _localHeader = null;
            _compressedData = null;
            _dataDescriptor = null;
            base.Dispose(disposing);

        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the name assigned to the entry.
        /// </summary>
        /// <value>
        /// The original path and name of the file that is/was compressed.
        /// </value>
        public override string Name
        {
            get
            {
                return _localHeader.FileName;
            }
            set
            {
                _localHeader.FileName = value;
            }
        }
        /// <summary>
        /// Gets the reference to the local file header record.
        /// </summary>
        /// <value>
        /// A <see cref="ZipLocalFileHeader"/> instance containing the information for the 
        /// file.
        /// </value>
        public ZipLocalFileHeader LocalHeader
        {
            get
            {
                return _localHeader;
            }
        }
        /// <summary>
        /// Gets or sets the compressed data content.
        /// </summary>
        public byte[] CompressedData
        {
            get
            {
                return _compressedData;
            }
            set
            {
                UnloadData();
                _compressedData = new byte[value.Length];
                Array.Copy(value, _compressedData, value.Length);
            }
        }
        /// <summary>
        /// Gets the reference to the data descriptor instance.
        /// </summary>
        public ZipDataDescriptor DataDescriptor
        {
            get
            {
                return _dataDescriptor;
            }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Decompresses the internal data array.
        /// </summary>
        /// <returns>
        /// A byte array containing the uncompressed data.
        /// </returns>
        public byte[] Decompress()
        {
            MemoryStream inStream = null;                   //Input stream.
            BinaryReader reader = null;                     //Data reader.
            DeflateStream decompressor = null;              //Deflate stream.
            byte[] returnValue = null;                      //Return value.

            //Create the objects.
            inStream = new MemoryStream(_compressedData);
            decompressor = new DeflateStream(inStream, CompressionMode.Decompress);
            reader = new BinaryReader(decompressor);

            //De-compress.
            returnValue = reader.ReadBytes(_localHeader.UncompressedSize);

            reader.Close();
            decompressor.Close();
            inStream.Close();

            return returnValue;
        }
        /// <summary>
        /// Compresses the specified original data.
        /// </summary>
        /// <param name="originalData">
        /// The original data to be compressed.
        /// </param>
        /// <remarks>
        /// The data is stored in the internal array and the header info is set
        /// for the new compression attributes.
        /// </remarks>
        public void Compress(byte[] originalData)
        {
            MemoryStream outStream = null;              //Output stream.
            DeflateStream compressor = null;           //Data compressor.
            Crc32 crc = null;                          //CRC-32 calculator.
            MsDosDate msDosDate;

            //Create the objects.
            outStream = new MemoryStream();
            compressor = new DeflateStream(outStream, CompressionMode.Compress, true);
            compressor.Write(originalData, 0, originalData.Length);
            compressor.Close();

            //Set values.
            _localHeader.UncompressedSize = originalData.Length;
            _localHeader.CompressionMethod = CompressionMethods.Deflated;
            msDosDate =(MsDosDate) Kernel32.FileTimeToDosDate(DateTime.Now.ToFileTime());
            _localHeader.LastModifiedDate = msDosDate.Date;
            _localHeader.LastModifiedTime = msDosDate.Time;

            //Compress.
            _compressedData = outStream.ToArray();
            _localHeader.CompressedSize = _compressedData.Length;
            _localHeader.VersionNeeded.Value = 20;

            crc = new Crc32();
            crc.Reset();
            crc.Update(originalData);
            _localHeader.Crc32 = Convert.ToUInt32(crc.Value);

            crc = null;
            compressor.Close();
            outStream.Close();

        }
        /// <summary>
        /// Loads the content for the instance from the specified stream.
        /// </summary>
        /// <param name="reader">An open <see cref="BinaryReader" /> used to read data from the
        /// underlying stream.</param>
        /// <returns>
        /// <b>true</b> if the load operation is successful; otherwise, <b>false</b>.
        /// </returns>
        public bool Load(BinaryReader reader)
        {
            return Load(reader, false, 0);
        }
        /// <summary>
        /// Loads the content for the instance from the specified stream.
        /// </summary>
        /// <param name="reader">An open <see cref="BinaryReader" /> used to read data from the
        /// underlying stream.</param>
        /// <param name="loadData">
        /// A value indicating whether to load the compressed data from the file into memory.
        /// </param>
        /// <param name="compressedSize">
        /// The size of the compressed data, in bytes.
        /// </param>
        /// <returns>
        /// <b>true</b> if the load operation is successful; otherwise, <b>false</b>.
        /// </returns>
        public bool Load(BinaryReader reader, bool loadData, int compressedSize)
    {
		bool returnValue = false;				//Return value.
		int nextSignature = 0;                  //Next item signature in PKZIP file.

		//Read the header data.
		returnValue = _localHeader.Load(reader);

		if (_localHeader.CompressedSize == 0) 
			_localHeader.CompressedSize = compressedSize;

		//Read the data
		if (returnValue) 
        {
			if (loadData) 
            {
				//Read the data.
				try
                {
					_compressedData = reader.ReadBytes(_localHeader.CompressedSize);
                }
				catch(IOException)
                {
                    returnValue = false;
                }
            }
            else
            {
				//Move the file pointer.
				reader.BaseStream.Seek(_localHeader.CompressedSize, SeekOrigin.Current);
            }

			//Peek at next signature.
			if (reader.BaseStream.Position < reader.BaseStream.Length) 
            {
				nextSignature = reader.ReadInt32();
				reader.BaseStream.Seek(-4, SeekOrigin.Current);
			}

			//Read the descriptor, if attached.
			if ((_localHeader.BitFlags.HasDataDescriptor) || (nextSignature == (int)ZipSignatures.DataDescriptor))
            {
				_dataDescriptor = new ZipDataDescriptor();
                _dataDescriptor.Load(reader);
            }
        }
		
		return returnValue;
    }
        /// <summary>
        /// Saves the content for the instance to the specified stream.
        /// </summary>
        /// <param name="writer">An open <see cref="BinaryWriter" /> used to write data to the
        /// underlying stream.</param>
        /// <returns>
        /// <b>true</b> if the save operation is successful; otherwise, <b>false</b>.
        /// </returns>
        public bool Save(BinaryWriter writer)
        {
            return Save(writer, false);
        }
        /// <summary>
        /// Saves the content for the instance to the specified stream.
        /// </summary>
        /// <param name="writer">An open <see cref="BinaryWriter" /> used to write data to the
        /// underlying stream.</param>
        /// <param name="saveData">
        /// A value indicating whether to write the compressed data.
        /// </param>
        /// <returns>
        /// <b>true</b> if the save operation is successful; otherwise, <b>false</b>.
        /// </returns>
        public bool Save(BinaryWriter writer, bool saveData)
        {
            bool returnValue = false;				//Return value.

            //Save the header data.
            returnValue = _localHeader.Save(writer);

            //Read the data
            if (returnValue)
            {
                //Only write content if present.
                if ((saveData) && ((_compressedData != null) && (_compressedData.Length > 0)))
                {
                    //Write the data.
                    try
                    {
                        writer.Write(_compressedData);
                        writer.Flush();
                    }
                    catch (IOException)
                    {
                        returnValue = false;
                    }
                }
                else
                {
                    //Move the file pointer.
                    writer.BaseStream.Seek(_localHeader.CompressedSize, SeekOrigin.Current);
                }

                //Read the descriptor, if attached.
                if (_dataDescriptor != null)
                    _dataDescriptor.Save(writer);
            }

            writer.Flush();

            return returnValue;

        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Unloads the byte array containing the compressed data content.
        /// </summary>
        private void UnloadData()
        {
            if (_compressedData != null)
            {
                Array.Clear(_compressedData, 0, _compressedData.Length);
                _compressedData = null;
                _compressedData = new byte[0];
                GC.Collect();

            }
        }
        #endregion
    }
}